﻿using System;
using System.Activities;
using System.Activities.Statements;
using System.Activities.Validation;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace WFExtended.Framework.Constraints
{
    /// <summary>
    ///  Activity Constraint for Activities Trees Introspection
    /// </summary>
    /// <typeparam name="A">Type of the activity to check</typeparam>
    public abstract class AActivityTypesConstraint<A> : AConstraint<A>
       where A : Activity
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="isWarning">Define if the constraint is a warning</param>
        /// <param name="notOperator">Define if the condition is a not operator</param>
        /// <param name="propertyName">Contraint property name</param>
        /// <param name="activityTypes">List of activity types to check</param>
        public AActivityTypesConstraint(bool isWarning, bool notOperator, string propertyName, params Type[] activityTypes)
            : base(isWarning, propertyName)
        {
            _activityTypes = activityTypes.ToList();
            _notOperator = notOperator;
        }

        /// <summary>
        /// List of activity types to check
        /// </summary>
        protected List<Type> _activityTypes;

        private bool _notOperator;

        /// <summary>
        /// Get the list of activities to validate
        /// </summary>
        /// <param name="validationContext">Context for the current node in the workflow tree</param>
        /// <returns></returns>
        public abstract Activity<IEnumerable<Activity>> GetActivitiesToValidate(DelegateInArgument<ValidationContext> validationContext);

        /// <summary>
        /// Get the error Message
        /// </summary>
        public abstract string ErrorMessage { get; }

        /// <summary>
        /// Get the activity Constraint
        /// </summary>
        /// <returns>>Return the activity Constraint</returns>
        public override System.Activities.Validation.Constraint<A> GetConstraint()
        {
            var currentActivity = new DelegateInArgument<A>();
            var activityToValidate = new DelegateInArgument<Activity>();
            var validationContext = new DelegateInArgument<ValidationContext>();
            var conditionResult = new Variable<bool>();
            var notOperator = new Variable<bool>();
           
            return new Constraint<A>
            {
                Body = new ActivityAction<A, ValidationContext>
                {
                    Argument1 = currentActivity,
                    Argument2 = validationContext,

                    Handler = new Sequence
                    {
                        Variables = 
                    {
                        conditionResult,
                        notOperator
                    },
                        Activities =
                    {
                        new Assign<bool>()
                        {
                            To = notOperator,
                            Value = _notOperator
                        },
                        new TryCatch()
                        {
                            Try =  new Sequence()
                            {
                                Activities = 
                                {
                                    new ForEach<Activity>
                                    {
                                        Values = GetActivitiesToValidate(validationContext),
                                        Body = new ActivityAction<Activity>()
                                        {
                                            Argument = activityToValidate,
                                            Handler = new If
                                            {
                                                Condition = new InArgument<bool>(env => IsAssignableFrom(_activityTypes,activityToValidate.Get(env).GetType())),
                                                Then = new Sequence
                                                {
                                                    Activities = 
                                                    {
                                                        new Assign<bool> 
                                                        { 
                                                            To = conditionResult, 
                                                            Value = true 
                                                        }
                                                    }
                                                }
                                            }    
                                        }
                                    },
                                    new If()
                                    {
                                        Condition = notOperator,
                                        Then = new Assign<bool>()
                                        {
                                            To = conditionResult,
                                            Value = true
                                        }
                                    }
                                }
                            }                          
                            ,
                            Catches = 
                            {
                                new Catch<ArgumentException>()
                                {
                                    Action = new ActivityAction<ArgumentException>()
                                    {
                                        Handler = new Assign<bool>()
                                        {
                                            To = conditionResult,
                                            Value = false
                                        }
                                    }
                                }
                            }
                        },
                        new AssertValidation
                        {
                            Assertion = conditionResult,
                            IsWarning = IsWarning,
                            Message = ErrorMessage,
                            PropertyName = PropertyName
                        }
                    }
                    }
                }
            };
        }

        private bool IsAssignableFrom(List<Type> typestoBeValidate, Type activityType)
        {
            bool result = false;

            foreach (Type typetoBeValidate in typestoBeValidate)
            {
                if (typetoBeValidate.IsGenericType
                    && activityType.IsGenericType)
                {
                    try
                    {

                        if (typetoBeValidate.GetGenericArguments()[0].IsGenericParameter)
                        {
                            result = typetoBeValidate.MakeGenericType(activityType.GetGenericArguments()).IsAssignableFrom(activityType);
                        }

                        else
                            result = typetoBeValidate.IsAssignableFrom(activityType);


                    }
                    catch { }

                }
                else if (typetoBeValidate.IsAssignableFrom(activityType))
                    result = true;

                if (result && !_notOperator)
                    return result;

                if (result && _notOperator)
                    throw new ArgumentException();

            }

            return result;
        }

    }
}
